<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>HashMap v8</title>
    <style>
        div{
            margin-top:10px;
        }
        html {
            font-size:12px;
        }
        input[type=text] {
            width: 50px;
        }
        input.long {
            width: 200px;
        }
        ::-webkit-input-placeholder{
            color:white;
        }
        table {
            border-collapse:collapse;
            border-spacing:0;
        }
        th {
            border-top: 2px solid black;
        }
        td {
            border-bottom: 1px solid black;
        }
    </style>
</head>
<body>
<div>
    <label for="capacity">capacity </label><input type="text" id="capacity" value="16" placeholder="请输入容量">
    <label for="factor">factor </label><input type="text" id="factor" value="0.75" placeholder="负载因子">
    <input type="button" value="init" id="initBtn">
    <input type="button" value="clear" id="clearBtn">
</div>
<div>
    <table id="tbl">
        <tr>
            <th>keys 以,分隔</th>
            <th colspan="5">计算哈希码</th>
            <th>操作</th>
        </tr>
        <tr>
            <td></td>
            <td></td>
            <td>原始hash</td>
            <td>二次hash</td>
            <td>桶下标</td>
            <td>固定hash</td>
            <td></td>
        </tr>
        <tr id="template" style="display:none">
            <td><input type="text" class="key long"></td>
            <td><input type="button" value="index" class="index"></td>
            <td><input type="text" readonly class="codes long"></td>
            <td><input type="text" readonly class="hashes long"></td>
            <td><input type="text" readonly class="indexes long"></td>
            <td><input type="text" value="-1" class="fix"></td>
            <td>
                <input type="button" value="put" class="put">
                <input type="button" value="remove" class="remove">
            </td>
        </tr>
    </table>
</div>
<div id="canvasDiv">
    <canvas id="canvas" width="1024" height="400"></canvas>
</div>
<script>
    const b_colors = ["#D24D57","#EB7347","#FC9D99","#26A65B","#AEDD81","#84AF9B","#00CCFF","#D0D0D0","#2C3E50"]
    const f_colors = ["white","white","black","white","black","white","black","black","white"]
    let idx = 1
    function addRow() {
        let row = document.getElementById("template").cloneNode(true)
        let parent = document.getElementById("template").parentNode
        row.removeAttribute("id")
        let random = Math.floor(Math.random()*9)
        row.style.display="table-row"
        row.style.backgroundColor = b_colors[random]
        row.style.color = f_colors[random]
        parent.appendChild(row)
    }
    addRow()
    document.getElementById("initBtn").onclick = function(){
        let capacity = document.getElementById("capacity").value;
        let factor = document.getElementById("factor").value;
        let url = `http://localhost:8080/v8/map/init?capacity=${capacity}&factor=${factor}`;
        fetch(url).then(r=>r.json()).then(json=>{
            loadData();
        });
    }
    document.getElementById("clearBtn").onclick = function(){
        let url = `http://localhost:8080/v8/map/clear`;
        fetch(url).then(r=>r.json()).then(json=>{
            loadData();
        });
    }
    document.getElementById("tbl").addEventListener("click", function(e){
        e.stopPropagation();
        let type = e.target.className;
        let tr = e.target.parentNode.parentNode;
        if(type === 'remove'){
            let key = tr.querySelector('.key').value;
            let fix = tr.querySelector('.fix').value;
            let url = `http://localhost:8080/v8/map/remove?key=${key}&fix=${fix}`;
            fetch(url).then(r=>r.json()).then(json=>{
                loadData();
            });
        } else if(type === 'put') {
            let key = tr.querySelector('.key').value;
            let fix = tr.querySelector('.fix').value;
            let url = `http://localhost:8080/v8/map/put?key=${key}&fix=${fix}`;
            fetch(url).then(r=>r.json()).then(json=>{
                loadData();
            });
        } else if(type === 'index') {
            let key = tr.querySelector(".key").value;
            let fix = tr.querySelector(".fix").value;
            let capacity = document.getElementById("capacity").value;
            let url = `http://localhost:8080/v8/map/index?key=${key}&fix=${fix}&capacity=${capacity}`;
            fetch(url).then(r=>r.json()).then(json=>{
                tr.querySelector(".codes").value=json.codes.join(",");
                tr.querySelector(".hashes").value=json.hashes.join(",");
                tr.querySelector(".indexes").value=json.indexes.join(",");
            });
        }
    })

    var Node = function (_data, _parent, _color) {
        this.data = _data;
        this.parent = _parent;
        this.color = _color;
        this.left = null;
        this.right = null;
    }

    let canvas = document.getElementById("canvas");
    let ctx = canvas.getContext("2d");

    function drawBuckets(buckets,bWidth,bHeight,xBegin,yBegin){
        let length = buckets.length;
        let tableCells = [];
        for(let i = 0; i < length; i++) {
            let x = xBegin + i*(bWidth + 2);
            let y = yBegin ;
            tableCells.push({x, yBegin});
            ctx.fillStyle="yellow";
            ctx.fillRect(x,yBegin,bWidth,bHeight);

            ctx.fillStyle="black";
            //ctx.font="14px Georgia";
            ctx.font = "bold 12px Arial";
            ctx.fillText(i,x+bWidth/2-5,y+bHeight/2+4);
        }

        for(let i = 0; i < length; i++) {
            let x = tableCells[i].x;
            let node = buckets[i];
            if("list"==node.type){
                for(let j=0; j<node.value.length; j++){
                    let y;
                    if(j==0) {
                        y = yBegin + bHeight + 2;
                    } else {
                        y = yBegin + bHeight + 2 + j*bHeight*2;
                    }
                    //ctx.fillStyle="LightBlue";
                    //ctx.fillRect(x,y,bWidth,bHeight);
                    ctx.beginPath();
                    ctx.fillStyle = "green";
                    ctx.arc(x+pointSize, y+pointSize, pointSize, (Math.PI / 180) * 0, (Math.PI / 180) * 360, false);
                    ctx.fill();
                    ctx.closePath();

                    //ctx.fillStyle="black";
                    //ctx.font="14px Georgia";
                    ctx.fillStyle = "white";
                    ctx.font = "bold 10px Arial";
                    ctx.fillText(node.value[j],x+bWidth/2-5,y+bHeight/2+4);

                    ctx.moveTo(x+bWidth/2, y+bHeight);
                    ctx.lineTo(x+bWidth/2, y+bHeight);
                    ctx.lineTo(x+bWidth/2, y+bHeight*2);
                    ctx.stroke();
                }
            } else if("tree"==node.type){
                let map = new Map();
                for(let j=0; j<node.value.length; j++){
                    let v = node.value[j];
                    map.set(v.data, new Node(v.data, null, v.color));
                }
                let root = null;
                for(let j=0; j<node.value.length; j++){
                    let v = node.value[j];
                    let n = map.get(v.data);
                    if(j == 0){
                        root = n;
                    }
                    n.left = map.get(v.left) || null;
                    n.right = map.get(v.right) || null;
                    n.parent = map.get(v.parent) || null;
                }
                SetPoint(root);
                PreOrder(root, SetPreOrder, ctx, x+9);
            }
        }
    }
    function loadData() {
        fetch("http://localhost:8080/v8/map/tree").then(r=>r.json()).then(data=>{
            const heightUnit = 140;
            canvas.width = 70 + (data.buckets.length*(18 + 2)*2);
            let details = data.details;
            if(details) {
                canvas.height = (details.length+1)*(heightUnit)*2;
            }
            drawBuckets(data.buckets, 18,13,0,0);
            if(details) {
                let lastHeight;
                for(let i = 0; i < details.length; i++) {
                    let d = details[i].list;
                    if(details[i].type==="from"){
                        lastHeight = (i+1)*heightUnit*2;
                        drawBuckets(d, 18,13,0,lastHeight);
                    } else if(details[i].type==="to"){
                        drawBuckets(d, 18,13,650,lastHeight);
                    }
                }
            }
        });
    }
    loadData();


    var pointSize = 8;
    var height = pointSize*4;//节点之间的高
    var width = pointSize;//节点之间的宽
    var tops = 25;//根节点离顶部的距离
    var foot = 40;//树离底部距离
    var spacing = 30;//树分别离两边的间距

    //let root = new Node("a",null,"black");
    //root.left = new Node("b",root,"red");
    //root.right = new Node("c",root,"red");
    //root.left.left = new Node("d",root.left,"black");
    //root.left.right = new Node("e",root.left,"black");
    //root.right.left = new Node("f",root.right,"red");
    //root.right.right = new Node("g",root.right,"red");
    //SetPoint(root);
    //PreOrder(root, SetPreOrder, ctx, canvas.width);

    function PreOrder(node, action, context, startX) {
        action(node, context, startX);
        if (node.left != null) {
            PreOrder(node.left, action, context, startX);
        }

        if (node.right != null) {
            PreOrder(node.right, action, context, startX);
        }
    }


    function SetCanvasWidthHeight(rootNode) {
        var level = Level(rootNode);
        return {
            height: height * level + tops + foot,
            width: Math.pow(2, level + 1) * width + spacing * 2
        };
    }

    function SetPreOrder(node, context, startX) {
        var container = drawArc(
            context,
            node.data,
            startX + width * node.nodePoint,
            (node.nodeLevel * height + parseInt(tops)),
            node.color);

        if (node.parent != null) {
            var line = linkNode(
                context,
                (startX + width * node.parent.nodePoint),
                (node.parent.nodeLevel * height + parseInt(tops)),
                (node.data, startX + width * node.nodePoint),
                (node.nodeLevel * height + parseInt(tops)));
        }
    }

    //生产节点
    function drawArc(context, number, x, y, color) {
        //圆
        context.beginPath();
        context.fillStyle = color;
        context.arc(x, y, pointSize, (Math.PI / 180) * 0, (Math.PI / 180) * 360, false);
        context.fill();
        context.closePath();

        //数字
        var textX = x - 3;
        var textY = y + 5;
        /*if (number < 10) {
            textX -= 5;
        } else if (number > 9 && number < 100) {
            textX -= 8;
        } else {
            textX -= 12;
        }*/

        context.fillStyle = "white";
        context.font = "bold 13px Arial";
        context.fillText(number + "", textX, textY);
    }

    //链接节点
    function linkNode(context, fatherNodeX, fatherNodeY, childrenNodeX, childrenNodeY) {
        drawLine(context, fatherNodeX, fatherNodeY + pointSize, childrenNodeX, childrenNodeY - pointSize);
    }

    //生产线
    function drawLine(context, x, y, toX, toY) {
        context.moveTo(x, y);
        context.lineTo(x, y);
        context.lineTo(toX, toY);
        context.stroke();
    }
    var maxLevel;
    var level;
    function Level(rootNode) {
        maxLevel = 0;
        level = 0;
        return levels(rootNode);
    }

    function levels(node) {
        if (node.left != null) {
            level++;
            levels(node.left);
        }
        maxLevel = Math.max(maxLevel, level);

        if (node.right != null) {
            level++;
            levels(node.right);
        }
        level--;
        return maxLevel;
    }

    function SetPoint(rootNode) {
        var thisMaxLevel = Level(rootNode);
        var childQuanty = Math.pow(2, thisMaxLevel);

        rootNode.nodeLevel = 0;
        rootNode.nodePoint = 0;

        if (rootNode.left != null) {
            setPointsLeft(rootNode.left, -1 * childQuanty / 2, 0, thisMaxLevel - 1);
        }

        if (rootNode.right != null) {
            setPointsRight(rootNode.right, childQuanty / 2, 0, thisMaxLevel - 1);
        }
    }

    function setPointsLeft(node, point, levels, thisMaxLevel) {
        ++levels;
        node.nodeLevel = levels;
        node.nodePoint = point;

        if (node.left != null) {
            setPointsLeft(node.left, point - Math.pow(2, thisMaxLevel - levels), levels, thisMaxLevel);
        }

        if (node.right != null) {
            setPointsLeft(node.right, point + Math.pow(2, thisMaxLevel - levels), levels, thisMaxLevel);
        }
    }

    function setPointsRight(node, point, levels, thisMaxLevel) {
        ++levels;
        node.nodeLevel = levels;
        node.nodePoint = point;

        if (node.left != null) {
            setPointsRight(node.left, point - Math.pow(2, thisMaxLevel - levels), levels, thisMaxLevel);
        }

        if (node.right != null) {
            setPointsRight(node.right, point + Math.pow(2, thisMaxLevel - levels), levels, thisMaxLevel);
        }
    }
</script>
</body>
</html>